//////////////////////////////////////////////////////////////////////////
//
// GAM203
// Input Manager
// InputMgr.cpp
// 
// Grant Johnson
// Qantm College
// 27/10/2010
//
//////////////////////////////////////////////////////////////////////////

#include "InputMgr.h"
#include <stdio.h>
#include <assert.h>

//Include DirectInput libraries
#pragma comment (lib, "dinput8")  
#pragma comment (lib, "dxguid")  

InputMgr* Singleton<InputMgr>::m_pInstance = NULL;

InputMgr::InputMgr()
: m_pDirectInput(NULL)
, m_pMouse(NULL)
, m_pKeyboard(NULL)
, m_mouseDataCount(0)
, m_mousePos(0,0,0)
{
	// Initialise our data buffer memory
	ZeroMemory(m_keyState, sizeof(m_keyState));
	ZeroMemory(m_keyPressed, sizeof(m_keyPressed));
	ZeroMemory(m_mouseData, sizeof(DIDEVICEOBJECTDATA) * MOUSE_BUFFER_SIZE);
	ZeroMemory(&m_mouseState, sizeof(m_mouseState));
	ZeroMemory(m_mouseButtonPressed, sizeof(m_mouseButtonPressed));
}

InputMgr::~InputMgr()
{
	Shutdown();
}


bool InputMgr::Setup(HINSTANCE a_hInst, HWND a_hWnd)
{
	//Initialise DirectInput
	HRESULT result = DirectInput8Create(a_hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pDirectInput, NULL);
	if (FAILED(result))
	{
		return false;
	}

	//Create mouse device
	result = m_pDirectInput->CreateDevice(GUID_SysMouse, &m_pMouse, NULL);
	if (FAILED(result))
	{
		Shutdown();
		return false;
	}

	//Create keyboard device
	result = m_pDirectInput->CreateDevice(GUID_SysKeyboard, &m_pKeyboard, NULL);
	if (FAILED(result))
	{
		Shutdown();
		return false;
	}

	//Set mouse data format
	result = m_pMouse->SetDataFormat(&c_dfDIMouse);
	if (FAILED(result))
	{
		Shutdown();
		return false;
	}

	//Set keyboard data format
	result = m_pKeyboard->SetDataFormat(&c_dfDIKeyboard);
	if (FAILED(result))
	{
		Shutdown();
		return false;
	}

	//Set mouse cooperative level
	result = m_pMouse->SetCooperativeLevel(a_hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
	if (FAILED(result))
	{
		Shutdown();
		return false;
	}

	//Set keyboard cooperative level
	result = m_pKeyboard->SetCooperativeLevel(a_hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND | DISCL_NOWINKEY);
	if (FAILED(result))
	{
		Shutdown();
		return false;
	}

	//Set mouse buffer size
	DIPROPDWORD propertyData;
	//Header data
	propertyData.diph.dwSize       = sizeof(DIPROPDWORD);
	propertyData.diph.dwHeaderSize = sizeof(DIPROPHEADER);
	propertyData.diph.dwObj        = 0;
	propertyData.diph.dwHow        = DIPH_DEVICE;
	//Property value data
	propertyData.dwData            = MOUSE_BUFFER_SIZE;

	//Set the property struct
	result = m_pMouse->SetProperty(DIPROP_BUFFERSIZE, &propertyData.diph);
	if (FAILED(result))
	{
		Shutdown();
		return false;
	}

	return true;
}


void InputMgr::Shutdown()
{
	// Shut down mouse device
	if (m_pMouse)
	{
		m_pMouse->Unacquire();
		m_pMouse->Release();
		m_pMouse = NULL;
	}

	// Shut down keyboard device
	if(m_pKeyboard)
	{
		m_pKeyboard->Unacquire();
		m_pKeyboard->Release();
		m_pKeyboard = NULL;
	}
	// Shut down DirectInput
	if(m_pDirectInput)
	{
		m_pDirectInput->Release();
		m_pDirectInput = NULL;
	}
}


void InputMgr::Update()
{
	// Read buffered mouse data (ie. mouse events)
	// m_mouseDataCount is an IN/OUT argument here:
	// IN: First we set its value to the maximum number of items we want (the number of elements in our buffer, m_mouseData)
	// OUT: GetDeviceData then sets its value to the number of items it put into our buffer
	m_mouseDataCount = MOUSE_BUFFER_SIZE;
	HRESULT result = m_pMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), m_mouseData, &m_mouseDataCount, 0);
	// Special case check for when device has been unacquired
	if (result == DIERR_INPUTLOST)
	{
		// Mouse input has been lost; attempt to re-acquire device and read again
		m_pMouse->Acquire();
		result = m_pMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), m_mouseData, &m_mouseDataCount, 0);
	}

	if (FAILED(result))
	{
		// Failed to get any data this frame
		m_mouseDataCount = 0;
		// No need to wipe m_mouseData here; setting m_mouseDataCount to 0 is enough
	}

	// Read instantaneous mouse state
	DIMOUSESTATE newMouseState;
	result = m_pMouse->GetDeviceState(sizeof(newMouseState), &newMouseState);
	// Special case check for when device has been unacquired
	if (result == DIERR_INPUTLOST)
	{
		// Mouse input has been lost; attempt to re-acquire device and read again
		m_pMouse->Acquire();
		result = m_pMouse->GetDeviceState(sizeof(newMouseState), &newMouseState);
	}

	if (FAILED(result))
	{
		// Failed to get any data this frame
		ZeroMemory(&m_mouseState, sizeof(m_mouseState));
		ZeroMemory(m_mouseButtonPressed, sizeof(m_mouseButtonPressed));
	}
	else
	{
		// Figure out which buttons are newly pressed
		for (int mouseButtonId = 0; mouseButtonId < MouseButton_MAX; ++mouseButtonId)
		{
			m_mouseButtonPressed[mouseButtonId] = (newMouseState.rgbButtons[mouseButtonId] & 0x80) && !(m_mouseState.rgbButtons[mouseButtonId] & 0x80);
		}

		// Take a copy of the new mouse state
		memcpy_s(&m_mouseState, sizeof(m_mouseState), &newMouseState, sizeof(newMouseState));
	}

	// Read instantaneous keyboard state into our buffer
	unsigned char newKeyState[KEYBOARD_BUFFER_SIZE];
	result = m_pKeyboard->GetDeviceState(KEYBOARD_BUFFER_SIZE, newKeyState);
	// Special case check for when device has been unacquired
	if (result == DIERR_INPUTLOST)
	{
		// Keyboard input has been lost; attempt to re-acquire device and read again
		m_pKeyboard->Acquire();
		result = m_pKeyboard->GetDeviceState(KEYBOARD_BUFFER_SIZE, newKeyState);
	}

	if (FAILED(result))
	{
		// Failed to get any data this frame
		ZeroMemory(m_keyState, sizeof(m_keyState));
		ZeroMemory(m_keyPressed, sizeof(m_keyPressed));
	}
	else
	{
		for(int i=0; i<KEYBOARD_BUFFER_SIZE; ++i)
		{
			//if the previous frame it wasn't pressed, set to false
			if(m_keyState[i] != 0x80)
			{
				m_keyPressed[i] = false;
			}
			//if previous frame, it was pressed, set to true
			else
			{
				m_keyPressed[i] = true;
			}
		}

		// Take a copy of the new keyboard state
		memcpy_s(&m_keyState, sizeof(m_keyState), &newKeyState, sizeof(newKeyState));
	}
}

void InputMgr::AcquireAll()
{
	//Acquire the mouse
	if (m_pMouse)
	{
		m_pMouse->Acquire();
	}

	//Acquire the keyboard
	if (m_pKeyboard)
	{
		m_pKeyboard->Acquire();
	}
}

void InputMgr::OnActivate(bool a_isActive)
{
	//If the program is active, acquire the keyboard and mouse
	if (a_isActive)
	{
		if (m_pMouse) { m_pMouse->Acquire(); }
		if (m_pKeyboard) { m_pKeyboard->Acquire(); }
	}
	//If the program is inactivate, unacquire the keyboard and mouse
	else
	{
		if (m_pMouse) { m_pMouse->Unacquire(); }
		if (m_pKeyboard) { m_pKeyboard->Unacquire(); }
	}
}

bool InputMgr::IsKeyDown(DWORD a_keyId)
{
	assert(a_keyId < KEYBOARD_BUFFER_SIZE);
	if(a_keyId < KEYBOARD_BUFFER_SIZE)
	{
		return m_keyPressed[a_keyId];
	}
	return false;
}

bool InputMgr::IsKeyPressed(DWORD a_keyId)
{
	assert(a_keyId < KEYBOARD_BUFFER_SIZE);

	if (a_keyId < KEYBOARD_BUFFER_SIZE)
	{
		if(!IsKeyDown(a_keyId))
		{
			// This data is from DirectInput - the high order bit (0x80) is set if the key is currently down
			// We use !! to safely turn a BYTE into a bool type
			return !!(m_keyState[a_keyId] & 0x80);
		}
	}
	return false;
}

long InputMgr::GetMouseData(DWORD a_mouseInputId)
{
	long retVal = 0;
	for (DWORD i = 0; i < m_mouseDataCount; ++i)
	{
		// For mouse data, dwOfs contains the type of event (we are looking for a_mouseInputId)
		if (m_mouseData[i].dwOfs == a_mouseInputId)
		{
			switch (a_mouseInputId)
			{
			case DIMOFS_X:
			case DIMOFS_Y:
			case DIMOFS_Z:
				retVal += m_mouseData[i].dwData;
				break;
			default:
				retVal = m_mouseData[i].dwData;
				break;
			}
		}
	}
	return retVal;
}

bool InputMgr::IsMouseButtonDown(MouseButtonId a_mouseButtonId)
{
	assert(a_mouseButtonId < MouseButton_MAX);
	if (a_mouseButtonId < MouseButton_MAX)
	{
		if(!IsMouseButtonPressed(a_mouseButtonId))
		{
			// This data is from DirectInput - the high-order bit is set if the button is pressed
			// We use !! to safely turn a BYTE into a bool type
			return !!(m_mouseState.rgbButtons[a_mouseButtonId] & 0x80);
		}
	}

	// Unknown button
	return 0;
}


bool InputMgr::IsMouseButtonPressed(MouseButtonId a_mouseButtonId)
{
	assert(a_mouseButtonId >= 0 && a_mouseButtonId < MouseButton_MAX);
	if (a_mouseButtonId >= 0 && a_mouseButtonId < MouseButton_MAX)
	{
		return m_mouseButtonPressed[a_mouseButtonId];
	}

	// Unknown button / out of range
	return 0;
}
void InputMgr::SetMousePos(D3DXVECTOR3 a_pos)
{
	m_mousePos = a_pos;
}
const D3DXVECTOR3 InputMgr::GetMousePos()
{
	return m_mousePos;
}
